﻿using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using UnityEngine;

public class Game : TSingleton<Game>
{
    Thread m_SendThread;
    Thread m_RecvThread;
    Thread m_KeepAliveThread;
    UdpClient m_Client = new UdpClient();
    Queue<byte[]> m_RecvMessageBytes = new Queue<byte[]>();
    public bool IsConnect { private set; get; }

    static Queue<PendingSendMessage> m_Messages = new Queue<PendingSendMessage>();
    static Queue<PendingSendMessage> m_PendingSendPool = new Queue<PendingSendMessage>();
    public static uint m_LocalUserID;

    PendingSendMessage TakePendingSendMsg(EMessageType type, BaseMessage m)
    {
        PendingSendMessage msg = null;
        if (m_PendingSendPool.Count > 0)
        {
            msg = m_PendingSendPool.Dequeue();
        }
        else
        {
            msg = new PendingSendMessage();
        }
        msg.m_Type = type;
        msg.m_Msg = m;
        return msg;
    }
    public uint TickCount { private set; get; }
    Dictionary<uint, Player> m_Players = new Dictionary<uint, Player>();

    void PutPendingSendMsg(PendingSendMessage msg)
    {
        msg.m_Msg = null;
        m_PendingSendPool.Enqueue(msg);
    }

    public void Clear()
    {
        if (m_RecvThread != null)
        {
            m_RecvThread.Abort();
            m_RecvThread = null;
        }
        if (m_SendThread != null)
        {
            m_SendThread.Abort();
            m_SendThread = null;
        }
        if (m_KeepAliveThread != null)
        {
            m_KeepAliveThread.Abort();
            m_KeepAliveThread = null;
        }
        m_Client.Close();
    }

    public void Init()
    {
        var ips = Launcher.Instance.m_ServerAddr.Split(':');
        m_Client.Connect(ips[0], int.Parse(ips[1]));
        m_Client.Client.SendBufferSize = 1024 * 1024;
        m_SendThread = new Thread(() =>
        {
            PendingSendMessage msg;
            byte[] sendBytes;
            while (true)
            {
                Thread.Sleep(33);
                //LogManager.Log("m_SendThread ");
                while (m_Messages.Count != 0)
                {
                    msg = m_Messages.Dequeue();
                    sendBytes = MessageManager.Pack(msg.m_Type, msg.m_Msg);
                    if (msg.m_Type == EMessageType.SYNC)
                    {
                        //LogManager.Log("send sync pos" + (msg.m_Msg as C2S_SyncMessage).m_Pos);
                    }
                    LogManager.Log("send " + msg.m_Type);
                    m_Client.Send(sendBytes, sendBytes.Length);
                    PutPendingSendMsg(msg);
                }
            }
        });
        m_SendThread.Start();

        m_RecvThread = new Thread(() =>
        {
            IPEndPoint ep = new IPEndPoint(IPAddress.Any, 1234);
            byte[] bs;
            while (true)
            {
                Thread.Sleep(1);
                //LogManager.Log("m_RecvThread ");
                if (m_Client.Available > 0)
                {
                    bs = m_Client.Receive(ref ep);
                    ReceiveMessage(bs);
                }
            }
        });
        m_RecvThread.Start();

        m_KeepAliveThread = new Thread(() =>
        {
            while (true)
            {
                Thread.Sleep(200);
                //LogManager.Log("m_KeepAliveThread ");
                if (!IsConnect)
                {
                    continue;
                }

                Send(EMessageType.PING, new BaseMessage());
            }
        });
        m_KeepAliveThread.Start();

        MessageDispatcher.Instance.Register<HelloMessage>(EMessageType.HELLO, (msg) =>
        {
            IsConnect = true;
            m_LocalUserID = msg.m_UserID;
            Send(EMessageType.ACK, new BaseMessage());
            Send(EMessageType.JOIN, new C2S_JoinMessage());
        });
        MessageDispatcher.Instance.Register<S2C_JoinMessage>(EMessageType.JOIN, (msg) =>
        {
            if (!m_Players.ContainsKey(msg.m_UserID))
            {
                var p = GameObject.Instantiate(Resources.Load<Player>("Player"));
                p.name = msg.m_UserID.ToString();
                p.Init(msg.m_UserID, msg.m_Pos);
                m_Players.Add(msg.m_UserID, p);

                if (p.IsLocalPlayer())
                {
                    // fake
                    var p1 = GameObject.Instantiate(Resources.Load<Player>("Player"));
                    p1.name = (msg.m_UserID + 1000).ToString();
                    p1.transform.position = msg.m_Pos;
                    m_Players.Add(msg.m_UserID + 1000, p1);
                    p1.Init(msg.m_UserID + 1000, msg.m_Pos);
                }
            }
        });
        MessageDispatcher.Instance.Register<S2C_SyncMessage>(EMessageType.SYNC, (msg) =>
        {
            //LogManager.Error("userid=" + msg.m_UserID + ", pos=" + msg.m_Pos);
            foreach (var item in msg.m_Players)
            {
                if (m_Players.ContainsKey(item.m_UserID))
                {
                    var p = m_Players[item.m_UserID];
                    if (p.IsLocalPlayer())
                    {
                        p = m_Players[item.m_UserID + 1000];
                        p.CacheTrans.position = item.m_Pos;
                    }
                    else
                    {
                        p.CacheTrans.position = item.m_Pos;
                    }
                }
            }
        });

        Send(EMessageType.HELLO, new HelloMessage());
    }

    public void Tick()
    {
        TickCount++;

        foreach (var item in m_Players)
        {
            item.Value.Tick();
        }

        if (m_RecvMessageBytes.Count > 0)
        {
            ProcessMessage(m_RecvMessageBytes.Dequeue());
        }

        if (m_SyncQueue.Count > 0)
        {
            var m = m_SyncQueue.Dequeue();
            LogManager.Log("proces sync queue count=" + m_SyncQueue.Count + ", seqId=" + m.m_Seq);
            MessageDispatcher.Instance.Invoke(EMessageType.SYNC, m);
        }
    }

    public void Send(EMessageType type, BaseMessage msg)
    {
        m_Messages.Enqueue(TakePendingSendMsg(type, msg));
    }

    public void ReceiveMessage(byte[] bs)
    {
        LogManager.Log("receive " + MessageManager.GetMessageType(bs) + ", seqId=" + MessageManager.Unpack(bs).m_Seq);
        m_RecvMessageBytes.Enqueue(bs);
    }

    Queue<S2C_SyncMessage> m_SyncQueue = new Queue<S2C_SyncMessage>(); 
    void ProcessMessage(byte[] bs)
    {
        LogManager.Log("process " + MessageManager.GetMessageType(bs));
        if (MessageManager.GetMessageType(bs) == EMessageType.SYNC)
        {
            var m = (MessageManager.Unpack(bs) as S2C_SyncMessage);
            var localMsg = m.m_Players.Find((temp=>temp.m_UserID==m_LocalUserID));
            LogManager.Log("recv sync pos " + m.m_Players.Count + ", seqId=" + m.m_Seq + ", localpos=" + localMsg.m_Pos);
            m_SyncQueue.Clear(); 
            //while (m.m_Players.Count > 5)
            //{
            //    var m0 = new S2C_SyncMessage();
            //    m0.m_Players.AddRange(m.m_Players.GetRange(0,5));
            //    m.m_Players.RemoveRange(0, 5); 
            //    m_SyncQueue.Enqueue(m0);
            //}
            m_SyncQueue.Enqueue(m);
        }
        else
        {
            MessageDispatcher.Instance.Invoke(MessageManager.GetMessageType(bs), MessageManager.Unpack(bs));
        }
    }
}
